package ac.il.technion.cs236369.hw5.core.servlets.instruments;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import ac.il.technion.cs236369.hw5.core.entities.Instrument;
import ac.il.technion.cs236369.hw5.core.entities.Role;
import ac.il.technion.cs236369.hw5.core.entities.User;
import ac.il.technion.cs236369.hw5.core.utils.OperationResult;

import com.google.gson.Gson;

public enum InstrumentOperation {

	INSTRUMENT_INSTRUMENT( "instrument_insert"){
		private ThreadLocal<Integer> id = new ThreadLocal<Integer>( );
		public InstrumentOperation execute( HttpServletRequest request) {
			EntityManager manager = emf.createEntityManager( );
			EntityTransaction transaction = manager.getTransaction( );
			transaction.begin( );
			try {
				String userLogin = request.getUserPrincipal( ).getName( );
				User user = manager.find( User.class, userLogin);
				Role role = user.getPermission( );
				Instrument instrument = new Instrument( );
				//instrument.setPermission(role);
				for ( InstrumentParameters $ : InstrumentParameters.values( )) {
					$.saveParameterValue(instrument, request, manager);
				}
				manager.persist(instrument);
				status.set( OperationResult.SUCCESS);
				transaction.commit( );
				id.set( instrument.getId( ));
				logger.info( "Instrument " + instrument.getDescription( ) + ", has been inserted.");
			} catch ( Exception e) {
				transaction.rollback( );
				status.set( OperationResult.ERROR);
				logger.error( e.getMessage( ));
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public InstrumentOperation resultOperation( HttpServletResponse response) {
			try {
				switch( status.get( )) {
					case SUCCESS : {
						response.getWriter( ).println( id.get( ));
						logger.info( "Reporting succefull end of + " + toString( ) + ". Instrument id: " + id);
						break;
					}
					case ERROR : {
						response.getWriter( ).println( "ERROR");
						logger.info( "Reporting failure end of + " + toString( ) + ". Instrument id: " + id);
						break;
					}
				}
			} catch (IOException e) {
				logger.error( e.getMessage( ));
			}
			return this;
		}
	},
	INSTRUMENT_UPDATE("instrument_update"){
		private ThreadLocal<Integer> id = new ThreadLocal<Integer>( );
		public InstrumentOperation execute( HttpServletRequest request) {
			EntityManager manager = emf.createEntityManager( );
			EntityTransaction transaction = manager.getTransaction();
			transaction.begin( );
			try {
				String userLogin = request.getUserPrincipal( ).getName( );
				User user = manager.find( User.class, userLogin);
				Role role = user.getPermission( );
				Query query = manager.createQuery( "SELECT i FROM Instrument i WHERE i.Id=:id");
				Instrument  instrument = (Instrument)query.setParameter("id", Integer.valueOf( request.getParameter( InstrumentParameters.INSTRUMENT_ID.getName( ))))
					 .getSingleResult( );
				if ( instrument != null && instrument.getPermission( ).getLevel( ) <= role.getLevel( )) {
					for ( InstrumentParameters $ : InstrumentParameters.values( )) {
						$.saveParameterValue(instrument, request, manager);
					}
					status.set( OperationResult.SUCCESS);
					manager.merge(instrument);
					id.set( instrument.getId( ));
				} else {
					status.set( OperationResult.ERROR);
					logger.error( "Instrument with id: " + request.getParameter( InstrumentParameters.INSTRUMENT_ID.getName( )) + " was not found.");
				}
				transaction.commit( );
			} catch ( Exception e) {
				status.set( OperationResult.ERROR);
				transaction.rollback( );
				logger.error( e.getMessage( ));
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public InstrumentOperation resultOperation( HttpServletResponse response) {
			try {
				switch( status.get( )) {
					case SUCCESS : {
						response.getWriter( ).println( id.get( ));
						logger.info( "Reporting succefull end of + " + toString( ) + ". Instrument id: " + id.get( ));
						break;
					}
					case ERROR : {
						response.getWriter( ).println( "ERROR");
						logger.info( "Reporting failure end of + " + toString( ) + ". Instrument id: " + id.get( ));
					}
				}
			} catch (Exception e) {
				logger.error( e.getMessage( ));
			}
			return this;
		}
	},
	INTRUMENT_DELETE( "instrument_delete") {
		public InstrumentOperation execute( HttpServletRequest request) {
			EntityManager manager = emf.createEntityManager( );
			EntityTransaction transaction = manager.getTransaction();
			transaction.begin( );
			try {
				String userLogin = request.getUserPrincipal( ).getName( );
				User user = manager.find( User.class, userLogin);
				Role role = user.getPermission( );
				status.set( OperationResult.SUCCESS);
				Query query = manager.createQuery( "DELETE FROM Instrument i WHERE i.Id=:id AND i.permission.level <= :level")
					.setParameter( "level", role.getLevel());
				int i = query.setParameter( "id", Integer.valueOf( request.getParameter( InstrumentParameters.INSTRUMENT_ID.getName( ))))
					 .getFirstResult( );
				if ( i > 0) {
					status.set( OperationResult.SUCCESS);
				} else {
					// Nothing to delete, hence wrong call
					// TODO: Probably need to consider not to
					// return error code in this case,
					// since nothing has been deleted, therefore
					// nothing bad happened.
					status.set( OperationResult.ERROR);
					logger.warn( "Nothing to delete. Instrument id: " + request.getParameter( InstrumentParameters.INSTRUMENT_ID.getName( )));
				}
				transaction.commit( );
			} catch( Exception e) {
				status.set( OperationResult.ERROR);
				transaction.rollback( );
				logger.error( e.getMessage( ));
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public InstrumentOperation resultOperation( HttpServletResponse response) {
			defaultResultImpl( response);
			return this;
		}
	},
	INSTRUMENT_LIST_ALL( "instrument_list_all") {
		private ThreadLocal<List<Instrument>> instruments = new ThreadLocal<List<Instrument>>( );
		private ThreadLocal<User> loggedIn = new ThreadLocal<User>( );
		@SuppressWarnings({ "unchecked", "cast" })
		public InstrumentOperation execute( HttpServletRequest request) {
			EntityManager manager = emf.createEntityManager( );
			loggedIn.set( manager.find( User.class, request.getUserPrincipal( ).getName( )));
			try {
				instruments.set((List<Instrument>)manager.createQuery( "SELECT i FROM Instrument i").getResultList( ));
				if ( instruments != null) {
					status.set( OperationResult.SUCCESS);
				} else {
					status.set( OperationResult.ERROR);
					logger.warn( "No instruments stored.");
				}
			} catch( Exception e) {
				status.set( OperationResult.ERROR);
				logger.error( e.getMessage( ));
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public InstrumentOperation resultOperation( HttpServletResponse response) {
			Gson gson = new Gson( );
			try {
				switch(status.get( )) {
					case SUCCESS: { 
						InstrumentItems items = new InstrumentItems( );
						for ( Instrument $ : instruments.get( )) {
							if ( $.getPermission( ).getLevel( ) >= loggedIn.get( ).getPermission( ).getLevel( ))
								items.getItems().add( new InstrumentEnvelope( $));
						}
						response.getWriter().println( gson.toJson(items));
						logger.info( "Report successeful end of operation + " + toString( ));
						break;
					}
					case ERROR: {
						response.getWriter( ).println( "ERROR");
						logger.info( "Report failure end of operation + " + toString( ));
						break;
					}
				}
			} catch (IOException e) {
				logger.error( e.getMessage( ));
			}
			return this;
		}
	},
	INSTRUMENT_EXTENDED_SEARCH( "instrument_extended_search") {
		private ThreadLocal< Collection<Instrument>> instruments = new ThreadLocal<Collection<Instrument>>( );
		private static final String query = "SELECT i FROM Instrument i";
		private ThreadLocal<User> loggedIn = new ThreadLocal<User>( );;
		
		private String build( HttpServletRequest request) {
			StringBuilder builder = new StringBuilder( );
			boolean whereInserted = false;
			builder.append( query);
			for ( QueryType $ : QueryType.values( )) {
				String str = null;
				if ( (str = request.getParameter( $.toString( ))) != null &&
						 request.getParameter( $.toString( )).length( ) > 0) {
					if ( !whereInserted) {
						builder.append( " WHERE");
						whereInserted = true;
					} else {
						builder.append( " AND");
					}
					if ( $ == QueryType.BY_PRICE) {
						builder.append( " i." + $.getClause( ) + " " + str );
					} else {
						builder.append( " i." + $.getClause( ) + " '" + str + "%'");
					}
				}
			}
			return builder.toString( );
		}
		
		@SuppressWarnings( { "cast", "unchecked" })
		public InstrumentOperation execute( HttpServletRequest request) {
			String sql = build( request);
			EntityManager manager = emf.createEntityManager( );
			loggedIn.set( manager.find( User.class, request.getUserPrincipal( ).getName( )));
			Query query = manager.createQuery( sql);
			instruments.set( (Collection<Instrument>)query.getResultList( ));
			status.set( OperationResult.SUCCESS);
			return this;
		}
		
		public InstrumentOperation resultOperation( HttpServletResponse response) {
			Gson gson = new Gson( );
			try {
				switch(status.get( )) {
					case SUCCESS: { 
						InstrumentItems items = new InstrumentItems( );
						for ( Instrument $ : instruments.get( )) {
							if ( $.getPermission( ).getLevel( ) >= loggedIn.get( ).getPermission( ).getLevel( ))
								items.getItems().add( new InstrumentEnvelope( $));
						}
						response.getWriter().println( gson.toJson(items));
						logger.info( "Report successeful end of operation + " + toString( ));
						break;
					}
					case ERROR: {
						response.getWriter( ).println( "ERROR");
						logger.info( "Report failure end of operation + " + toString( ));
						break;
					}
				}
			} catch (IOException e) {
				logger.error( e.getMessage( ));
			}
			return this;
		}
	},
	DUMMY( "dummy") {
		public InstrumentOperation execute( HttpServletRequest request) {
			return  this;
		}
		
		public InstrumentOperation resultOperation( HttpServletResponse response) {
			try {
				response.getWriter( ).println( "ERROR");
			} catch (IOException e) {
				logger.error( e.getMessage( ));
			}
			return this;
		}
	};
	
	protected enum QueryType {
		BY_TYPE( "type") {
			public String getClause( ) {
				return "type LIKE ";
			}
		},
		BY_DESCRIPTION( "description") {
			public String getClause( ) {
				return "description LIKE ";
			}
		},
		BY_PRICE( "usagePrice") {
			public String getClause( ) {
				return "usagePrice <= ";
			}
		},
		UNDEFINED( "undefined") {
			public String getClause( ) {
				return "";
			}
		};
		
		private String name;
		
		private static Map<String, QueryType> map = new HashMap<String, QueryType>( );
		
		static {
			for ( QueryType $ : QueryType.values( )) {
				map.put( $.name, $);
			}
		}
		
		private QueryType( String name) {
			this.name = name;
		}
		
		public abstract String getClause( );
		
		@Override
		public String toString( ) {
			return name;
		}
	};
	
	protected EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaProvider");
	
	private String name;
	
	protected ThreadLocal<OperationResult> status = new ThreadLocal<OperationResult>( ) {
		@Override
		protected OperationResult initialValue( ) {
			return OperationResult.SUCCESS;
		}
	};

	private static Map<String, InstrumentOperation> map = new HashMap<String, InstrumentOperation>( );
	
	abstract public InstrumentOperation execute( HttpServletRequest request);
	
	abstract public InstrumentOperation resultOperation( HttpServletResponse response);
	
	private InstrumentOperation( String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString( ) {
		return name;
	}
	
	public static InstrumentOperation fromString( String name) {
		InstrumentOperation operation = map.get(name.toUpperCase( ));
		if ( operation != null) {
			return operation;
		}
		return map.get( "DUMMY");
	}
	
	protected final Logger logger = Logger.getLogger( InstrumentOperation.class);
	
	static {
		for ( InstrumentOperation operation : InstrumentOperation.values()) {
			map.put( operation.getName().toUpperCase( ), operation);
		}
	};
	
	protected void defaultResultImpl( HttpServletResponse response) {
		try {
			switch( status.get( )) {
				case SUCCESS: {
					response.getWriter().println( "SUCCESS");
					logger.info( "Reporting succefull end of + " + toString( ));
					break;
				}
				case ERROR: {
					response.getWriter().println( "ERROR");
					logger.info( "Reporting failure end of + " + toString( ));
					break;
				}
			};
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
}
